package cn.demoncat.util.web.util;

import cn.demoncat.util.exception.*;
import cn.demoncat.util.exception.constant.ExceptionTypeEnum;
import cn.demoncat.util.lang.StringUtil;
import cn.demoncat.util.lang.entity.Result;
import cn.demoncat.util.web.TokenUtil;
import cn.demoncat.util.web.entity.ExceptionLog;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.validation.BindException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.time.DateTimeException;
import java.util.Arrays;

/**
 * 统一异常处理工具
 * 
 * @author 延晓磊
 *
 * @since 2018年4月11日
 */
public class ExceptionHandlerUtil {

	private static final Logger LOG = LoggerFactory.getLogger(ExceptionHandlerUtil.class);
	
	// 不属于错误的IO异常
	private static final String[] NOT_IO_ERROR = {"断开的管道", "broken pipe"};
	// StringCloud微服务调用异常
	private static final String[] REST_ERROR = {"feign.FeignException", "com.netflix.hystrix.exception.HystrixRuntimeException", "com.netflix.client.ClientException"};
 	// 通用Warn异常名称规范
	private static final String[] WARN_EXCEPTION_NAME = {"WarnRuntimeException", "WarnException"};
	// 通用Error异常名称规范
	private static final String[] ERROR_EXCEPTION_NAME = {"ErrorRuntimeException", "ErrorException"};
	
	/**
	 * 异常处理
	 * 
	 * @param ex
	 * @param logStatus 是否记录日志
	 *
	 * @return 不论Controller返回值是什么类型，都会返回Result；所以在Controller返回值非Result时（Result字符串/Result子类/Result相似类），至少保证格式相同（包含status和msg），方便前端统一处理
	 * 
	 * @author 延晓磊
	 *
	 * @since 2018年4月11日
	 */
	public static Result handle(Throwable ex, boolean logStatus) {
		// 异常类名
		String exception = ex.getClass().getName();

		// Warn：正常的业务错误、参数错误、操作错误、权限错误等
		Result error = warnHandler(ex, exception, logStatus);
		if (error == null) {
			// Error：业务错误、程序错误、连接错误、未知错误等
			error = errorHandler(ex, exception, logStatus);
		}

		// 返回错误结果
		return error;
	}

	/**
	 * 功能描述：异常日志消息
	 *
	 * @param type		异常分类
	 * @param exception	异常类型
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2017年6月8日
	 */
	private static String getLogMsg(ExceptionTypeEnum type, String exception) {
		// 日志消息
		return new ExceptionLog(type, exception, TokenUtil.getAccess()).toString();
	}

	/**
	 * Warn处理
	 *
	 * @param ex
	 * @param exception
	 * @param logStatus 是否记录日志
	 * @return Result，非Warn返回null
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年6月8日
	 */
	private static Result warnHandler(Throwable ex, String exception, boolean logStatus){
		ExceptionTypeEnum type;
		Result error;
		if (ex instanceof BizRuntimeException || ex instanceof BizException) {
			// 业务异常：正常错误-直接输出错误信息
			type = ExceptionTypeEnum.BIZ;
			error = Result.error(ex.getMessage());
		} else if (ex instanceof ParamRuntimeException) {
			// 前端参数校验失败：输出前端错误，要求前端更改
			type = ExceptionTypeEnum.PARAM;
			error = Result.error(ex.getMessage());
		} else if (ex instanceof TokenRuntimeException) {
			// Token
			type = ExceptionTypeEnum.REQUEST;
			switch (((TokenRuntimeException) ex).getCode()) {
				case "1":
				case "2":
					// Token为空/Token非法
					error = Result.error(ex.getMessage());
					break;
				default:
					// 未登录
					error = Result.ERROR_LOGIN;
					break;
			}
		} else if (ex instanceof AuthRuntimeException) {
			// 没有权限
			type = ExceptionTypeEnum.AUTH;
			error = Result.ERROR_AUTH;
		} else if (ex instanceof DateTimeException) {
			// 时间格式错误：输出前端错误，要求前端更改
			type = ExceptionTypeEnum.PARAM;
			error = Result.TIME_FORMAT_ERROR;
		} else if (ex instanceof NumberFormatException || ex instanceof MethodArgumentTypeMismatchException || ex instanceof BindException) {
			// 参数类型错误：输出前端错误，要求前端更改
			type = ExceptionTypeEnum.PARAM;
			error = Result.error(Result.ERROR_PARAM_TYPE.getMsg());
		} else if (ex instanceof DuplicateKeyException) {
			// 数据库主键、唯一字段重复异常：输出数据重复
			type = ExceptionTypeEnum.PARAM;
			error = Result.ERROR_DATA_REPEAT;
		} else if (ex instanceof DataIntegrityViolationException) {
			// 数据库字段数据太长、字段数据为空（应在业务中判断）、字段数据类型不对应（须在开发中处理）
			type = ExceptionTypeEnum.PARAM;
			if (ex.getMessage().contains("cannot be null")) {
				// Column 'xxx' cannot be null
				error = Result.ERROR_PARAM_NULL;
			}else if (ex.getMessage().contains("too long")) {
				// Data truncation: Data too long for column 'xxx'
				error = Result.ERROR_PARAM_LONG;
			}else{
				// 参数类型错误
				error = Result.ERROR_PARAM_ERROR;
			}
		} else if (ex instanceof WarnRuntimeException || ex instanceof WarnException || Arrays.stream(WARN_EXCEPTION_NAME).anyMatch(exception::endsWith)) {
			// 其它警告
			type = ExceptionTypeEnum.SERVER;
			error = Result.error(StringUtil.toDefault(ex.getMessage(), ExceptionTypeEnum.SERVER.getMsg()));
		}else {
			// 非Warn异常
			return null;
		}
		// 输出日志
		if (logStatus) {
			LOG.warn(getLogMsg(type, exception), ex);
		}
		return error;
	}

	/**
	 * Error 处理
	 *
	 * @param ex
	 * @param exception
	 * @param logStatus 是否记录日志
	 * @return Result，非Warn返回null
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年6月8日
	 */
	private static Result errorHandler(Throwable ex, String exception, boolean logStatus) {
		ExceptionTypeEnum type;
		Result error;
		if (ex instanceof BizErrorRuntimeException || ex instanceof BizErrorException) {
			// 业务异常：非正常错误-直接输出错误信息
			type = ExceptionTypeEnum.BIZ;
			error = Result.error(ex.getMessage());
		} else if (ex instanceof NullPointerException) {
			// 空指针：服务端处理不严谨
			type = ExceptionTypeEnum.SERVER;
			error = Result.ERROR_DATA_NULL;
		} else if (ex instanceof DataAccessException || ex instanceof DbRuntimeException) {
			// 数据库操作异常：输出系统错误
			type = ExceptionTypeEnum.DB;
			error = Result.FAIL;
		} else if (ex instanceof RestRuntimeException || ex instanceof RestClientException || ArrayUtils.contains(REST_ERROR, exception)) {
			// 请求远程服务失败：输出系统错误
			type = ExceptionTypeEnum.REST;
			error = Result.FAIL;
		} else if (ex instanceof ConnRuntimeException || ex instanceof UnknownHostException || ex instanceof SocketException || ex instanceof InterruptedIOException) {
			// 网络连接异常(域名解析不到，IP找不到，连接超时)：输出系统错误
			type = ExceptionTypeEnum.CONNECTION;
			error = Result.FAIL;
		} else if (ex instanceof IOException || ex instanceof IoRuntimeException) {
			// IO读写失败或受限制：输出系统错误
			type = ExceptionTypeEnum.IO;
			error = Result.FAIL;
			// 忽略断开的管道异常
			if (StringUtils.isNotBlank(ex.getMessage())) {
				String ems = ex.getMessage().toLowerCase();
				if (Arrays.stream(NOT_IO_ERROR).anyMatch(ems::contains)) {
					return error;
				}
			}
		} else if (ex instanceof ErrorRuntimeException || ex instanceof ErrorException || Arrays.stream(ERROR_EXCEPTION_NAME).anyMatch(exception::endsWith)) {
			// 其它异常
			type = ExceptionTypeEnum.SERVER;
			error = Result.error(StringUtil.toDefault(ex.getMessage(), ExceptionTypeEnum.SERVER.getMsg()));
		} else {
			// 未知异常
			type = ExceptionTypeEnum.UNAWARE;
			error = Result.FAIL;
		}
		// 输出日志
		if (logStatus) {
			LOG.error(getLogMsg(type, exception), ex);
		}
		return error;
	}
}
